package app

import (
	"bytes"
	"context"
	"encoding/json"
	"gitee.com/zhucheer/orange/logger"
	"gitee.com/zhucheer/orange/request"
	"gitee.com/zhucheer/orange/session"
	"gitee.com/zhucheer/orange/utils"
	"io"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"
)

type Context struct {
	CsrfToken         string
	OrangeInput       *request.OrangeInput
	response          http.ResponseWriter
	request           *http.Request
	path              string
	pnames            []string
	pvalues           []string
	ctx               context.Context
	session           session.Store
	responseBody      *bytes.Buffer
	responseStatus    int
	panicMsg          interface{}
	ms                time.Duration
	userParams        map[string]interface{}
	redirect          string
	view              *viewTmpl
	afterDelayDoFuncs []afterDelayFunc
	mutx              sync.Mutex
}

type afterDelayFunc struct {
	doFunc func(ctx *Context)
	delay  time.Duration
}

func NewCtx(ctx context.Context, w http.ResponseWriter, r *http.Request) *Context {
	return &Context{
		ctx:          ctx,
		response:     w,
		request:      r,
		pnames:       make([]string, 0),
		pvalues:      make([]string, *routers.maxParam),
		view:         newViewTmpl(),
		responseBody: new(bytes.Buffer),
	}
}

func (c *Context) SetRW(w http.ResponseWriter, r *http.Request) {
	c.response = w
	c.request = r
}

func (c *Context) Redirect(tourl string) {
	c.redirect = tourl
}

func (c *Context) HttpError(error string, code int) {
	http.Error(c.response, error, code)
}

func (c *Context) ResponseWrite(b []byte) error {
	_, err := c.responseBody.Write(b)
	return err
}

func (c *Context) ResponseBody() []byte {
	return c.responseBody.Bytes()
}

func (c *Context) ResponseReset() {
	c.responseBody.Reset()
}

func (c *Context) ResponseWriteHeader(code int) {
	c.response.WriteHeader(code)
}

func (c *Context) ResponseHeader() http.Header {
	return c.response.Header()
}

func (c *Context) SetPanic(panicMsg interface{}) {
	c.panicMsg = panicMsg
}

func (c *Context) SetResponseStatus(status int) {
	c.responseStatus = status
}

func (c *Context) ResponseStatus() int {
	return c.responseStatus
}

func (c *Context) Header() http.Header {
	return c.request.Header
}

func (c *Context) Session() session.Store {
	if c.session == nil {
		logger.Warning("session is not open")
		return nil
	}
	return c.session
}

func (c *Context) GetPathParam(name string) (val string) {
	return c.getPathParam(name)
}

func (c *Context) GetPathParamToInt(name string) (val int) {
	val, _ = strconv.Atoi(c.getPathParam(name))
	return
}

func (c *Context) getPathParam(name string) (val string) {
	var paramIndex *int
	for k, item := range c.pnames {
		if item == name {
			indexNum := k
			paramIndex = new(int)
			paramIndex = &indexNum
			break
		}
	}
	if paramIndex != nil && *paramIndex < len(c.pvalues) {
		val = c.pvalues[*paramIndex]
	}
	return
}

func (c *Context) SetCtxParam(key string, value interface{}) {
	c.mutx.Lock()
	defer c.mutx.Unlock()

	if c.userParams == nil {
		c.userParams = make(map[string]interface{})
	}
	c.userParams[key] = value
}

func (c *Context) GetCtxParam(key string) (val interface{}) {
	if c.userParams == nil {
		return
	}
	if val, ok := c.userParams[key]; ok {
		return val
	}
	return
}

func (c *Context) SetCookie(cookie *http.Cookie) {
	http.SetCookie(c.response, cookie)
}

func (c *Context) Request() *http.Request {
	return c.request
}

func (c *Context) SetContext(ctx context.Context) context.Context {
	c.ctx = ctx
	return c.ctx
}

func (c *Context) Context() context.Context {
	return c.ctx
}

func (c *Context) RoutePath() string {
	return c.path
}

func (c *Context) GetMs() time.Duration {
	return c.ms
}

func (c *Context) AddDelayAfterDo(handlerFunc func(ctx *Context), delay time.Duration) {
	c.afterDelayDoFuncs = append(c.afterDelayDoFuncs, afterDelayFunc{
		handlerFunc, delay,
	})
}

func (c *Context) ToJson(data interface{}) error {
	c.response.Header().Set("Content-Type", "application/json;charset=UTF-8")
	jsonBytes, err := json.Marshal(data)
	if err != nil {
		c.response.WriteHeader(500)
		c.response.Write([]byte(err.Error()))
		return err
	}
	c.ResponseWrite(jsonBytes)
	return nil
}

func (c *Context) ToString(data string) error {
	c.response.Header().Set("Content-Type", "text/html;charset=UTF-8")

	c.ResponseWrite([]byte(data))
	return nil
}

// 通过文件流方式输出图片
func (c *Context) ToImage(filePath string) error {
	if exists, _ := utils.FileExists(filePath); exists == false {
		return NotFoundHandler(c)
	}

	file, err := os.Open(filePath)
	if err != nil {
		return NotFoundHandler(c)
	}
	defer file.Close()

	ext := strings.TrimLeft(path.Ext(filePath), ".")
	buf := make([]byte, 32)
	c.response.Header().Set("Content-Type", "image/"+ext)
	for {
		_, err := file.Read(buf)
		if err != nil {
			if err == io.EOF {
				break
			} else {
				panic(err)
			}
		}
		c.response.Write(buf)
	}

	return nil
}

// 通过文件流方式输出
func (c *Context) ToFile(filePath string, fileName string) error {
	if exists, _ := utils.FileExists(filePath); exists == false {
		return NotFoundHandler(c)
	}

	file, err := os.Open(filePath)
	if err != nil {
		return NotFoundHandler(c)
	}
	defer file.Close()

	if fileName == "" {
		fileName = path.Base(filePath)
	}
	buf := make([]byte, 32)
	c.response.Header().Set("Content-Disposition", "attachment;filename="+fileName)
	c.response.Header().Set("Content-Type", "	application/octet-stream")
	for {
		_, err := file.Read(buf)
		if err != nil {
			if err == io.EOF {
				break
			} else {
				panic(err)
			}
		}
		c.response.Write(buf)
	}

	return nil
}
